package com.minimalist.basic.config.fileHandler.handler.impl;

import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.minimalist.basic.config.fileHandler.handler.FileHandler;
import com.minimalist.common.config.exception.BusinessException;
import com.minimalist.common.config.fileHandler.entity.MinIOFileEntity;
import com.minimalist.common.entity.basic.po.MFile;
import com.minimalist.common.entity.basic.po.MStorage;
import com.minimalist.common.utils.UnqIdUtil;
import io.minio.*;
import io.minio.http.Method;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

/**
 * MinIO文件处理器
 *
 * @author 11987
 */
@Component
public class MinIOFileHandler implements FileHandler{
    private static final Logger log = LoggerFactory.getLogger(MinIOFileHandler.class);

    // MinioClient和MinIOFileEntity在每个方法中动态创建，无需注入

    @Override
    public boolean isHandler(String storageType){
        return "minio".equalsIgnoreCase(storageType);
    }

    @Override
    public String valid(String storageConfig){
        try{
            if (StrUtil.isBlank(storageConfig)){
                throw new BusinessException("MinIO存储配置不能为空");
            }

            MinIOFileEntity config = JSONUtil.toBean(storageConfig, MinIOFileEntity.class);

            if (StrUtil.isBlank(config.getAccessKey())){
                throw new BusinessException("MinIO访问密钥不能为空");
            }
            if (StrUtil.isBlank(config.getSecretKey())){
                throw new BusinessException("MinIO私有密钥不能为空");
            }
            if (StrUtil.isBlank(config.getEndPoint())){
                throw new BusinessException("MinIO域名不能为空");
            }
            if (StrUtil.isBlank(config.getBucketName())){
                throw new BusinessException("MinIO桶名称不能为空");
            }

            return JSONUtil.toJsonStr(config);
        } catch (Exception e){
            log.error("MinIO配置验证失败: {}", e.getMessage(), e);
            throw new BusinessException("MinIO配置格式错误");
        }
    }

    @Override
    public MFile uploadFile(MultipartFile multipartFile, Integer fileSource, MStorage storage){
        try{
            MinIOFileEntity config = JSONUtil.toBean(storage.getStorageConfig(), MinIOFileEntity.class);

            // 创建MinIO客户端
            String fileName;
            try (MinioClient client = MinioClient.builder()
                    .endpoint(config.getEndPoint())
                    .credentials(config.getAccessKey(), config.getSecretKey())
                    .build()){

                // 检查存储桶是否存在，如果不存在则创建
                boolean found = client.bucketExists(BucketExistsArgs.builder().bucket(config.getBucketName()).build());
                if (!found){
                    client.makeBucket(MakeBucketArgs.builder().bucket(config.getBucketName()).build());
                }

                // 文件后缀
                String fileSuffix = FileNameUtil.extName(multipartFile.getOriginalFilename());
                // 新文件名
                String newFileName = IdUtil.objectId() + "." + fileSuffix;
                // 生成文件名
                fileName = newFileName;

                // 上传文件
                client.putObject(
                        PutObjectArgs.builder()
                                .bucket(config.getBucketName())
                                .object(fileName)
                                .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                                .contentType(multipartFile.getContentType())
                                .build());
            }

            // 创建文件记录
            MFile mFile = new MFile();
            mFile.setFileId(UnqIdUtil.uniqueId());
            mFile.setFileName(multipartFile.getOriginalFilename());
            mFile.setNewFileName(fileName);
            mFile.setFileSize(multipartFile.getSize());
            mFile.setFileType(multipartFile.getContentType());
            mFile.setFileSource(fileSource);
            mFile.setStorageId(storage.getStorageId());
            mFile.setFilePath(fileName);
            // 设置文件访问URL
            String fileUrl = config.getEndPoint() + "/" + config.getBucketName() + "/" + fileName;
            mFile.setFileUrl(fileUrl);

            return mFile;
        } catch (Exception e){
            log.error("MinIO文件上传失败: {}", e.getMessage(), e);
            throw new BusinessException("文件上传失败");
        }
    }

    @Override
    public boolean deleteFile(MFile file, MStorage storage){
        try{
            MinIOFileEntity config = JSONUtil.toBean(storage.getStorageConfig(), MinIOFileEntity.class);

            // 创建MinIO客户端
            try (MinioClient client = MinioClient.builder()
                    .endpoint(config.getEndPoint())
                    .credentials(config.getAccessKey(), config.getSecretKey())
                    .build()){

                client.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(config.getBucketName())
                                .object(file.getFilePath())
                                .build());
            }

            return true;
        } catch (Exception e){
            log.error("MinIO文件删除失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public String getPreviewUrl(MFile mFile, MStorage storage, Integer expiry){
        try{
            MinIOFileEntity config = JSONUtil.toBean(storage.getStorageConfig(), MinIOFileEntity.class);

            // 创建MinIO客户端
            MinioClient client = MinioClient.builder()
                    .endpoint(config.getEndPoint())
                    .credentials(config.getAccessKey(), config.getSecretKey())
                    .build();

            return client.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(config.getBucketName())
                            .object(mFile.getFilePath())
                            .expiry(expiry != null ? expiry : 7, TimeUnit.DAYS)
                            .build());
        } catch (Exception e){
            log.error("MinIO获取文件URL失败: {}", e.getMessage(), e);
            throw new BusinessException("获取文件URL失败");
        }
    }

    @Override
    public void downloadFile(MFile mFile, MStorage storage, HttpServletResponse response){
        try{
            MinIOFileEntity config = JSONUtil.toBean(storage.getStorageConfig(), MinIOFileEntity.class);

            // 创建MinIO客户端
            MinioClient client = MinioClient.builder()
                    .endpoint(config.getEndPoint())
                    .credentials(config.getAccessKey(), config.getSecretKey())
                    .build();

            // 获取文件流
            InputStream inputStream = client.getObject(
                    GetObjectArgs.builder()
                            .bucket(config.getBucketName())
                            .object(mFile.getFilePath())
                            .build());

            // 设置响应头
            response.setContentType(mFile.getFileType());
            response.setHeader("Content-Disposition", "attachment; filename=" + mFile.getFileName());

            // 写入响应流
            try (OutputStream outputStream = response.getOutputStream()){
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1){
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
            }

            inputStream.close();
        } catch (Exception e){
            log.error("MinIO文件下载失败: {}", e.getMessage(), e);
            throw new BusinessException("文件下载失败");
        }
    }

    @Override
    public MFile uploadLocalFile(String fileName, String newFileName, Integer fileSource, MStorage storage){
        try{
            MinIOFileEntity config = JSONUtil.toBean(storage.getStorageConfig(), MinIOFileEntity.class);

            // 创建MinIO客户端
            MinioClient client = MinioClient.builder()
                    .endpoint(config.getEndPoint())
                    .credentials(config.getAccessKey(), config.getSecretKey())
                    .build();

            // 检查存储桶是否存在，如果不存在则创建
            boolean found = client.bucketExists(BucketExistsArgs.builder().bucket(config.getBucketName()).build());
            if (!found){
                client.makeBucket(MakeBucketArgs.builder().bucket(config.getBucketName()).build());
            }

            // 获取本地文件信息
            File localFile = new File(fileName);
            if (!localFile.exists()){
                throw new BusinessException("本地文件不存在: " + fileName);
            }

            // 使用新文件名，如果没有提供则使用原文件名
            String objectName = StrUtil.isNotBlank(newFileName) ? newFileName : localFile.getName();

            // 上传文件
            client.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(config.getBucketName())
                            .object(objectName)
                            .filename(fileName)
                            .build());

            // 创建文件记录
            MFile mFile = new MFile();
            mFile.setFileId(UnqIdUtil.uniqueId());
            mFile.setFileName(localFile.getName());
            mFile.setNewFileName(objectName);
            mFile.setFileSize(localFile.length());
            mFile.setFileSource(fileSource);
            mFile.setStorageId(storage.getStorageId());
            mFile.setFilePath(objectName);
            // 设置文件访问URL
            String fileUrl = config.getEndPoint() + "/" + config.getBucketName() + "/" + objectName;
            mFile.setFileUrl(fileUrl);

            return mFile;
        } catch (Exception e){
            log.error("MinIO本地文件上传失败: {}", e.getMessage(), e);
            throw new BusinessException("本地文件上传失败");
        }
    }
}

